package jxtend.processor.eclipse.visitor.handler.impl;

import jxtend.annotation.keyword.workinprogress.cast;
import jxtend.processor.eclipse.utils.EclipseProcessorUtils;
import jxtend.processor.eclipse.visitor.handler.ASTNodeHandlerVisitorContext;
import jxtend.processor.eclipse.visitor.handler.AbstractASTNodeVisitorHandler;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;

@SuppressWarnings("restriction")
public class CastAnnotationHandler extends AbstractASTNodeVisitorHandler {

	@Override
	public boolean endVisitBeforeResolve(ASTNodeHandlerVisitorContext context, ASTNode node, Scope scope) {
		if (!(node instanceof LocalDeclaration) || (((LocalDeclaration) node).type == null)) {
			return true;
		}
		
		LocalDeclaration localDeclaration = (LocalDeclaration) node;

		Annotation castAnnotation = EclipseProcessorUtils.findAnnotation(localDeclaration.annotations, cast.class);
		if (castAnnotation == null) {
			return true;
		}

		TypeReference type = localDeclaration.type;

		Expression initialization = localDeclaration.initialization;
		if (initialization == null) {
			//TODO: log error / warning that a cast cannot be used without an initialization expression
			return true;			
		}

		if (initialization instanceof CastExpression) {
			//TODO: log error / warning that no explicit casts are allowed together with an @cast
			return true;
		}

		if ((type.resolvedType == null) || !type.resolvedType.isValidBinding()
				|| (initialization.resolvedType == null) || !initialization.resolvedType.isValidBinding()) {
			return true;
		}

		if ((type.resolvedType instanceof BaseTypeBinding)
				|| (initialization.resolvedType instanceof BaseTypeBinding)) {
			//TODO: log error / warning that @cast is not supported for primitive types		
		}

		if ((type.resolvedType instanceof ArrayBinding)
				|| (initialization.resolvedType instanceof ArrayBinding)) {
			if (type.resolvedType.getClass().equals(initialization.resolvedType)) {
				//TODO: log error / warning for unnecessary @cast annotation				
			} else {
				if (!initialization.resolvedType.equals(scope.getJavaLangObject())) {
					//TODO: log error / warning that cast is not allowed
					return true;
				}
			}
		}

		if ((type.resolvedType instanceof ReferenceBinding)
				&& (initialization.resolvedType instanceof ReferenceBinding)) {
			ReferenceBinding declarationType = (ReferenceBinding) type.resolvedType;
			ReferenceBinding initializationType = (ReferenceBinding) initialization.resolvedType;
			
			if (CharOperation.equals(declarationType.compoundName, initializationType.compoundName)) {
				if ((declarationType instanceof ParameterizedTypeBinding)
						&& !(declarationType instanceof RawTypeBinding)
						&& (initializationType instanceof RawTypeBinding)) {
					//TODO: log error / warning that @unchecked should be used instead				
				} else {
					//TODO: log error / warning for unnecessary @cast annotation				
				}

				return true;
			}
		}

		CastExpression castExpression = new CastExpression(initialization, type);
		castExpression.expectedType = type.resolvedType;

		localDeclaration.initialization = castExpression;

		return true;
	}
}
